[id].vue 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. <template>
  2. <div class="admin--branch-form">
  3. <div v-if="isLoading" class="admin--loading">데이터를 불러오는 중...</div>
  4. <form v-else @submit.prevent="handleSubmit" class="admin--form">
  5. <!-- 지점명 -->
  6. <div class="admin--form-group">
  7. <label class="admin--form-label"
  8. >지점명 <span class="admin--required">*</span></label
  9. >
  10. <input
  11. v-model="formData.name"
  12. type="text"
  13. class="admin--form-input"
  14. placeholder="지점명을 입력하세요"
  15. required
  16. />
  17. </div>
  18. <!-- 대표번호 -->
  19. <div class="admin--form-group">
  20. <label class="admin--form-label"
  21. >대표번호 <span class="admin--required">*</span></label
  22. >
  23. <input
  24. v-model="formData.main_phone"
  25. type="tel"
  26. class="admin--form-input"
  27. placeholder="02-1234-5678"
  28. required
  29. />
  30. </div>
  31. <!-- 주소 -->
  32. <div class="admin--form-group">
  33. <label class="admin--form-label"
  34. >주소 <span class="admin--required">*</span></label
  35. >
  36. <input
  37. v-model="formData.address"
  38. type="text"
  39. class="admin--form-input"
  40. placeholder="주소를 입력하세요"
  41. required
  42. />
  43. </div>
  44. <!-- 상세주소 -->
  45. <div class="admin--form-group">
  46. <label class="admin--form-label">상세주소</label>
  47. <input
  48. v-model="formData.detail_address"
  49. type="text"
  50. class="admin--form-input"
  51. placeholder="상세주소를 입력하세요"
  52. />
  53. </div>
  54. <!-- 위도/경도 -->
  55. <div class="admin--form-group">
  56. <label class="admin--form-label">위치 좌표</label>
  57. <div class="admin--coordinate-group">
  58. <div class="admin--coordinate-item">
  59. <label>위도</label>
  60. <input
  61. v-model.number="formData.latitude"
  62. type="number"
  63. step="any"
  64. class="admin--form-input"
  65. placeholder="37.5665"
  66. />
  67. </div>
  68. <div class="admin--coordinate-item">
  69. <label>경도</label>
  70. <input
  71. v-model.number="formData.longitude"
  72. type="number"
  73. step="any"
  74. class="admin--form-input"
  75. placeholder="126.9780"
  76. />
  77. </div>
  78. </div>
  79. </div>
  80. <!-- 영업시간 -->
  81. <div class="admin--form-group">
  82. <label class="admin--form-label">영업시간</label>
  83. <textarea
  84. v-model="formData.business_hours"
  85. class="admin--form-textarea"
  86. rows="3"
  87. placeholder="평일: 09:00 - 18:00&#10;주말: 10:00 - 17:00"
  88. ></textarea>
  89. </div>
  90. <!-- 버튼 영역 -->
  91. <div class="admin--form-actions">
  92. <button type="submit" class="admin--btn admin--btn-primary" :disabled="isSaving">
  93. {{ isSaving ? "저장 중..." : "확인" }}
  94. </button>
  95. <button type="button" class="admin--btn admin--btn-secondary" @click="goToList">
  96. 목록
  97. </button>
  98. </div>
  99. <!-- 성공/에러 메시지 -->
  100. <div v-if="successMessage" class="admin--alert admin--alert-success">
  101. {{ successMessage }}
  102. </div>
  103. <div v-if="errorMessage" class="admin--alert admin--alert-error">
  104. {{ errorMessage }}
  105. </div>
  106. </form>
  107. </div>
  108. </template>
  109. <script setup>
  110. import { ref, onMounted } from "vue";
  111. import { useRoute, useRouter } from "vue-router";
  112. definePageMeta({
  113. layout: "admin",
  114. middleware: ["auth"],
  115. });
  116. const route = useRoute();
  117. const router = useRouter();
  118. const { get, put } = useApi();
  119. const isLoading = ref(true);
  120. const isSaving = ref(false);
  121. const successMessage = ref("");
  122. const errorMessage = ref("");
  123. const formData = ref({
  124. name: "",
  125. main_phone: "",
  126. address: "",
  127. detail_address: "",
  128. latitude: null,
  129. longitude: null,
  130. business_hours: "",
  131. });
  132. // 데이터 로드
  133. const loadBranch = async () => {
  134. isLoading.value = true;
  135. const id = route.params.id;
  136. const { data, error } = await get(`/branch/${id}`);
  137. if (data?.success && data?.data) {
  138. const branch = data.data;
  139. formData.value = {
  140. name: branch.name || "",
  141. main_phone: branch.main_phone || "",
  142. address: branch.address || "",
  143. detail_address: branch.detail_address || "",
  144. latitude: branch.latitude || null,
  145. longitude: branch.longitude || null,
  146. business_hours: branch.business_hours || "",
  147. };
  148. }
  149. isLoading.value = false;
  150. };
  151. // 폼 제출
  152. const handleSubmit = async () => {
  153. successMessage.value = "";
  154. errorMessage.value = "";
  155. // 유효성 검사
  156. if (!formData.value.name) {
  157. errorMessage.value = "지점명을 입력하세요.";
  158. return;
  159. }
  160. if (!formData.value.main_phone) {
  161. errorMessage.value = "대표번호를 입력하세요.";
  162. return;
  163. }
  164. if (!formData.value.address) {
  165. errorMessage.value = "주소를 입력하세요.";
  166. return;
  167. }
  168. isSaving.value = true;
  169. try {
  170. const id = route.params.id;
  171. const { data, error } = await put(`/branch/${id}`, formData.value);
  172. if (error || !data?.success) {
  173. errorMessage.value = error?.message || data?.message || "수정에 실패했습니다.";
  174. } else {
  175. successMessage.value = data.message || "지점이 수정되었습니다.";
  176. setTimeout(() => {
  177. router.push("/site-manager/branch/list");
  178. }, 1000);
  179. }
  180. } catch (error) {
  181. errorMessage.value = "서버 오류가 발생했습니다.";
  182. console.error("Save error:", error);
  183. } finally {
  184. isSaving.value = false;
  185. }
  186. };
  187. // 목록으로 이동
  188. const goToList = () => {
  189. router.push("/site-manager/branch/list");
  190. };
  191. onMounted(() => {
  192. loadBranch();
  193. });
  194. </script>
  195. <style scoped>
  196. .admin--coordinate-group {
  197. display: flex;
  198. gap: 16px;
  199. }
  200. .admin--coordinate-item {
  201. flex: 1;
  202. }
  203. .admin--coordinate-item label {
  204. display: block;
  205. margin-bottom: 8px;
  206. font-size: 14px;
  207. color: #666;
  208. }
  209. </style>